﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Dotnet.Utils.Utility.CollectionUtil
{
    public static class ExpressionExtension
    {
        public static Expression Property(this Expression expression, string propertyName)
        {
            Expression result;
            if (propertyName.All((t) => t != '.'))
            {
                result = Expression.Property(expression, propertyName);
            }
            else
            {
                string[] array = propertyName.Split(new char[]
                {
                    '.'
                });
                Expression expression2 = null;
                for (int i = 0; i < array.Length; i++)
                {
                    if (i == 0)
                    {
                        expression2 = Expression.Property(expression, array[0]);
                    }
                    else
                    {
                        expression2 = expression2.Property(array[i]);
                    }
                }
                result = expression2;
            }
            return result;
        }

        public static Expression Property(this Expression expression, MemberInfo member)
        {
            return Expression.MakeMemberAccess(expression, member);
        }

        public static Expression And(this Expression left, Expression right)
        {
            Expression result;
            if (left == null)
            {
                result = right;
            }
            else if (right == null)
            {
                result = left;
            }
            else
            {
                result = Expression.AndAlso(left, right);
            }
            return result;
        }

        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
        {
            Expression<Func<T, bool>> result;
            if (left == null)
            {
                result = right;
            }
            else if (right == null)
            {
                result = left;
            }
            else
            {
                result = left.Compose(right, new Func<Expression, Expression, Expression>(Expression.AndAlso));
            }
            return result;
        }

        public static Expression Or(this Expression left, Expression right)
        {
            Expression result;
            if (left == null)
            {
                result = right;
            }
            else if (right == null)
            {
                result = left;
            }
            else
            {
                result = Expression.OrElse(left, right);
            }
            return result;
        }

        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
        {
            Expression<Func<T, bool>> result;
            if (left == null)
            {
                result = right;
            }
            else if (right == null)
            {
                result = left;
            }
            else
            {
                result = left.Compose(right, new Func<Expression, Expression, Expression>(Expression.OrElse));
            }
            return result;
        }

        public static object Value<T>(this Expression<Func<T, bool>> expression)
        {
            return expression.GetValue();
        }

        public static object GetValue(this Expression expression)
        {
            object result;
            if (expression == null)
            {
                result = null;
            }
            else
            {
                ExpressionType nodeType = expression.NodeType;
                switch (nodeType)
                {
                    case ExpressionType.Call:
                        return GetMethodCallExpressionValue(expression);

                    case ExpressionType.Coalesce:
                    case ExpressionType.Conditional:
                    case ExpressionType.ConvertChecked:
                    case ExpressionType.Divide:
                    case ExpressionType.ExclusiveOr:
                    case ExpressionType.Invoke:
                    case ExpressionType.LeftShift:
                    case ExpressionType.ListInit:
                        goto IL_102;
                    case ExpressionType.Constant:
                        return GetConstantExpressionValue(expression);

                    case ExpressionType.Convert:
                        return ((UnaryExpression)expression).Operand.GetValue();

                    case ExpressionType.Equal:
                    case ExpressionType.GreaterThan:
                    case ExpressionType.GreaterThanOrEqual:
                    case ExpressionType.LessThan:
                    case ExpressionType.LessThanOrEqual:
                        break;

                    case ExpressionType.Lambda:
                        return ((LambdaExpression)expression).Body.GetValue();

                    case ExpressionType.MemberAccess:
                        return GetMemberValue((MemberExpression)expression);

                    default:
                        switch (nodeType)
                        {
                            case ExpressionType.Not:
                                if (expression.Type == typeof(bool))
                                {
                                    return false;
                                }
                                return null;

                            case ExpressionType.NotEqual:
                                break;

                            default:
                                goto IL_102;
                        }
                        break;
                }
                return ((BinaryExpression)expression).Right.GetValue();
            IL_102:
                result = null;
            }
            return result;
        }

        private static object GetMethodCallExpressionValue(Expression expression)
        {
            MethodCallExpression methodCallExpression = (MethodCallExpression)expression;
            object value = methodCallExpression.Arguments.FirstOrDefault().GetValue();
            object result;
            if (value != null)
            {
                result = value;
            }
            else if (methodCallExpression.Object == null)
            {
                result = methodCallExpression.Type.InvokeMember(methodCallExpression.Method.Name, BindingFlags.InvokeMethod, null, null, null);
            }
            else
            {
                result = methodCallExpression.Object.GetValue();
            }
            return result;
        }

        private static object GetMemberValue(MemberExpression expression)
        {
            object result;
            if (expression == null)
            {
                result = null;
            }
            else
            {
                FieldInfo fieldInfo = expression.Member as FieldInfo;
                if (fieldInfo != null)
                {
                    object constantExpressionValue = GetConstantExpressionValue(expression.Expression);
                    result = fieldInfo.GetValue(constantExpressionValue);
                }
                else
                {
                    PropertyInfo propertyInfo = expression.Member as PropertyInfo;
                    if (propertyInfo == null)
                    {
                        result = null;
                    }
                    else if (expression.Expression == null)
                    {
                        result = propertyInfo.GetValue(null);
                    }
                    else
                    {
                        object memberValue = GetMemberValue(expression.Expression as MemberExpression);
                        if (memberValue == null)
                        {
                            if (propertyInfo.PropertyType == typeof(bool))
                            {
                                result = true;
                            }
                            else
                            {
                                result = null;
                            }
                        }
                        else
                        {
                            result = propertyInfo.GetValue(memberValue);
                        }
                    }
                }
            }
            return result;
        }

        private static object GetConstantExpressionValue(Expression expression)
        {
            ConstantExpression constantExpression = (ConstantExpression)expression;
            return constantExpression.Value;
        }

        public static Expression Equal(this Expression left, Expression right)
        {
            return Expression.Equal(left, right);
        }

        public static Expression Equal(this Expression left, object value)
        {
            return left.Equal(left.Constant(value));
        }

        public static ConstantExpression Constant(this Expression expression, object value)
        {
            MemberExpression memberExpression = expression as MemberExpression;
            ConstantExpression result;
            if (memberExpression == null)
            {
                result = Expression.Constant(value);
            }
            else
            {
                result = Expression.Constant(value, memberExpression.Type);
            }
            return result;
        }

        public static Expression NotEqual(this Expression left, Expression right)
        {
            return Expression.NotEqual(left, right);
        }

        public static Expression NotEqual(this Expression left, object value)
        {
            return left.NotEqual(left.Constant(value));
        }

        public static Expression Greater(this Expression left, Expression right)
        {
            return Expression.GreaterThan(left, right);
        }

        public static Expression Greater(this Expression left, object value)
        {
            return left.Greater(left.Constant(value));
        }

        public static Expression GreaterEqual(this Expression left, Expression right)
        {
            return Expression.GreaterThanOrEqual(left, right);
        }

        public static Expression GreaterEqual(this Expression left, object value)
        {
            return left.GreaterEqual(left.Constant(value));
        }

        public static Expression Less(this Expression left, Expression right)
        {
            return Expression.LessThan(left, right);
        }

        public static Expression Less(this Expression left, object value)
        {
            return left.Less(left.Constant(value));
        }

        public static Expression LessEqual(this Expression left, Expression right)
        {
            return Expression.LessThanOrEqual(left, right);
        }

        public static Expression LessEqual(this Expression left, object value)
        {
            return left.LessEqual(left.Constant(value));
        }

        public static Expression StartsWith(this Expression left, object value)
        {
            return left.Call("StartsWith", new Type[]
            {
                typeof(string)
            }, new object[]
            {
                value
            });
        }

        public static Expression EndsWith(this Expression left, object value)
        {
            return left.Call("EndsWith", new Type[]
            {
                typeof(string)
            }, new object[]
            {
                value
            });
        }

        public static Expression Contains(this Expression left, object value)
        {
            return left.Call("Contains", new Type[]
            {
                typeof(string)
            }, new object[]
            {
                value
            });
        }

        public static Expression Call(this Expression instance, string methodName, params Expression[] values)
        {
            return Expression.Call(instance, instance.Type.GetTypeInfo().GetMethod(methodName), values);
        }

        public static Expression Call(this Expression instance, string methodName, params object[] values)
        {
            Expression result;
            if (values == null || values.Length == 0)
            {
                result = Expression.Call(instance, instance.Type.GetTypeInfo().GetMethod(methodName));
            }
            else
            {
                result = Expression.Call(instance, instance.Type.GetTypeInfo().GetMethod(methodName), values.Select(new Func<object, ConstantExpression>(Expression.Constant)));
            }
            return result;
        }

        public static Expression Call(this Expression instance, string methodName, Type[] paramTypes, params object[] values)
        {
            Expression result;
            if (values == null || values.Length == 0)
            {
                result = Expression.Call(instance, instance.Type.GetTypeInfo().GetMethod(methodName, paramTypes));
            }
            else
            {
                result = Expression.Call(instance, instance.Type.GetTypeInfo().GetMethod(methodName, paramTypes), values.Select(new Func<object, ConstantExpression>(Expression.Constant)));
            }
            return result;
        }

        internal static Expression<T> Compose2<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
        {
            Dictionary<ParameterExpression, ParameterExpression> map = first.Parameters.Select((f, i) => new
            {
                f,
                s = second.Parameters[i]
            }).ToDictionary(p => p.s, p => p.f);
            Expression arg = ParameterRebinder.ReplaceParameters(map, second.Body);
            return Expression.Lambda<T>(merge(first.Body, arg), first.Parameters);
        }

        public static Expression<TDelegate> ToLambda<TDelegate>(this Expression body, params ParameterExpression[] parameters)
        {
            Expression<TDelegate> result;
            if (body == null)
            {
                result = null;
            }
            else
            {
                result = Expression.Lambda<TDelegate>(body, parameters);
            }
            return result;
        }
    }

    public class ParameterRebinder : ExpressionVisitor
    {
        public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
        {
            _map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
        }

        public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
        {
            return new ParameterRebinder(map).Visit(exp);
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            ParameterExpression parameterExpression;
            if (_map.TryGetValue(node, out parameterExpression))
            {
                node = parameterExpression;
            }
            return base.VisitParameter(node);
        }

        private readonly Dictionary<ParameterExpression, ParameterExpression> _map;
    }
}